package nodes;

import java.util.LinkedList;
import java.util.List;

import compiler.Compiler;
import compiler.SymbolTable;

import descriptors.AbstractDescr;
import descriptors.ProcedureDescr;

public class ProcedureDeclarationNode extends AbstractNode {

	
	private IdentNode ident;
	private ListNode<StatementNode> body;
	private ListNode<FPSectionNode> formalParameters;
	private DeclarationsNode declarations;
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public ProcedureDeclarationNode(ProcedureHeadingNode procedureHeading,ProcedureBodyNode procedureBody,int line ,int column){
		super(line,column);
		this.ident=procedureHeading.ident();
		this.formalParameters=procedureHeading.formalParameters();
		this.body=procedureBody.statementSeq();
		this.declarations=procedureBody.declarations();
	}
	
	@Override
	public AbstractDescr compile(
			SymbolTable symbolTable, Compiler compiler) {
		int savedAddess=compiler.address();
		compiler.incLevel();
		compiler.setAddress(-3); // use relative addresses from now on
		List<AbstractDescr> params=new LinkedList<AbstractDescr>();
		if(formalParameters != null){
			for(FPSectionNode elem:formalParameters){
				List<AbstractDescr> fpDescr=elem.compileParams(symbolTable,compiler);
				params.addAll(fpDescr);
			}
		}
		int lengthParBlock=-(3+compiler.address());
		String name=ident.ident();
		int start=compiler.newLabel();
		ProcedureDescr procDescr=new ProcedureDescr(name,-1,lengthParBlock,start,params);
        symbolTable.set(compiler.level()-1,name, procDescr);
		compiler.setAddress(0); // Reset addresses for compiling the declarations as they are at the beginning of the frame
		declarations.compile(symbolTable, compiler);
		int frameSize=compiler.address(); // framesize is the size needed for local variables, i.e. the address space used for compiling the declarations
		
		// save registers
		compiler.write("LABEL, "+start);
		compiler.write("PUSHREG, RK");
		compiler.write("PUSHREG, FP");
		compiler.write("PUSHI, " + compiler.level());
		compiler.write("PUSHREG, SL");
		
		// set pointers
		// set frame pointer
		compiler.write("GETSP");
		compiler.write("SETFP");
		
		// set static level
		compiler.write("GETFP");
		compiler.write("PUSHI, "+compiler.level());
		compiler.write("SETSL");
		
		// set stack pointer
		compiler.write("GETSP");
		compiler.write("PUSHI, "+frameSize);
		compiler.write("ADD");
		compiler.write("SETSP");
		
		// compile statements
		body.compile(symbolTable,compiler);
		
		// reset sp
		compiler.write("GETFP");
		compiler.write("SETSP");
		
		// reset registers
		compiler.write("PUSHI, " + compiler.level());
		compiler.write("POPREG, SL");
		compiler.write("POPREG, FP");
		compiler.write("POPREG, RK");
		
		
		// Set stack pointer to start / before parameter part
		compiler.write("GETSP");
		compiler.write("PUSHI, "+lengthParBlock);
		compiler.write("SUB");
		compiler.write("SETSP");
		
		// Stack freigeben
//		compiler.write("REDUCE, "+(3+lengthParBlock+frameSize));
		
		// return
		compiler.write("RET");
		
		// reset base address
		compiler.setAddress(savedAddess);
		symbolTable.resetLevel(compiler.level());
		compiler.decLevel();
		procDescr=new ProcedureDescr(name,frameSize,lengthParBlock,start,params);
		symbolTable.set(name, procDescr);
		//System.out.println("Set ProcedureDescr: "+name);
		//procDescr.print();
		//symbolTable.print();
		return null;
	}

	@Override
	public void print() {
		trace("ProcedureDeclarationNode");
		ident.print();
		if(formalParameters!=null)
			formalParameters.print();
		if(declarations!=null)
			declarations.print();
		if(body!=null)
			body.print();
		unindent();
	}

}
